﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Sockets;

namespace LuceneApiServerLib
{
    /// <summary>
    ///     表示索引查询对象
    ///     不可继承
    /// </summary>
    /// <typeparam name="T">Model类型</typeparam>
    public sealed class LnQuery<T>
    {
        private readonly string indexName;
        private readonly string keywords;
        private readonly HashSet<LnMatchField> matchFields = new HashSet<LnMatchField>();
        // private readonly IFastSession Session;
        private readonly LnProperty[] properties;
        private readonly HashSet<LnSortField> sortFields = new HashSet<LnSortField>();
        private readonly string url;
        private int _skipCount;
        private int _takeCount = 50;

        /// <summary>
        /// 是否可以设置高亮
        /// </summary>
        private bool _canSetHighLight = false;

        /// <summary>
        ///     索引查询对象
        /// </summary>
        /// <param name="properties">模型的属性</param>
        /// <param name="indexName">索引名称</param>
        /// <param name="keywords">关键字</param>
        internal LnQuery(string linkurl, LnProperty[] properties, string indexName, string keywords)
        {
            url = linkurl;
            this.properties = properties;
            this.indexName = indexName;
            this.keywords = keywords;
        }

        /// <summary>
        ///     跳过元素
        /// </summary>
        /// <param name="count">元素个数</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns></returns>
        public LnQuery<T> Skip(int count = 1)
        {
            if (_takeCount > 0)
                _skipCount = count < 0 ? 0 : count;
            return this;
        }

        /// <summary>
        /// 设置可以高亮，m默认无法高亮
        /// </summary>
        public LnQuery<T> SetCanHighLight()
        {
            _canSetHighLight = true;
            return this;
        }

        /// <summary>
        ///     取元素
        /// </summary>
        /// <param name="count">元素个数</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns></returns>
        public LnQuery<T> Take(int count = 1)
        {
            if (count <= 0)
            {
                _skipCount = 1;
            }
            _takeCount = count;
            return this;
        }

        /// <summary>
        ///     获取属性名
        /// </summary>
        /// <typeparam name="TKey"></typeparam>
        /// <param name="keySelector"></param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns></returns>
        private string GetPropertyName<TKey>(Expression<Func<T, TKey>> keySelector)
        {
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }
            var body = keySelector.Body as MemberExpression;
            if (body == null)
            {
                throw new ArgumentException("keySelector不是MemberExpression类型");
            }
            if (!body.Member.IsDefined(typeof(FieldAttribute), false))
            {
                throw new ArgumentException(string.Format("类型{0}的属性{1}不含FieldAttribute特性，不能进行相关检索", typeof(T).Name,
                    body.Member.Name));
            }
            return body.Member.Name;
        }

        /// <summary>
        ///     获取属性
        /// </summary>
        /// <typeparam name="TKey"></typeparam>
        /// <param name="keySelector"></param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns></returns>
        private LnProperty GetLnProperty<TKey>(Expression<Func<T, TKey>> keySelector)
        {
            var name = GetPropertyName(keySelector);
            var property = properties.FirstOrDefault(item => item.Name == name);
            if (property == null)
            {
                throw new ArgumentException(string.Format("类型{0}的属性{1}为不支持的检索类型：{2}", typeof(T).Name, name,
                    typeof(TKey).Name));
            }
            return property;
        }

        /// <summary>
        ///     设置搜索匹配的文本字段
        /// </summary>
        /// <param name="keySelector">字段</param>
        /// <param name="isTerm">是否条件</param>
        /// <param name="termKeyword"></param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns></returns>
        public LnQuery<T> MatchField(Expression<Func<T, string>> keySelector, bool isTerm = false,
            string termKeyword = null)
        {
            return MatchField(keySelector, null, null, 0, isTerm, termKeyword);
        }

        /// <summary>
        ///     设置搜索匹配的文本字段
        ///     并设置关键字Html元素的class名
        /// </summary>
        /// <param name="keySelector">字段</param>
        /// <param name="keywordClass">class名</param>
        /// <param name="isTerm">是否条件</param>
        /// <param name="termKeyword"></param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns></returns>
        public LnQuery<T> MatchField(Expression<Func<T, string>> keySelector, string keywordClass, bool isTerm = false,
            string termKeyword = null)
        {
            return MatchField(keySelector, keywordClass, 0, isTerm, termKeyword);
        }

        /// <summary>
        ///     设置搜索匹配的文本字段
        ///     并设置关键字Html元素的class名
        ///     返回其最佳摘要段
        /// </summary>
        /// <param name="keySelector">字段</param>
        /// <param name="keywordClass">class名</param>
        /// <param name="fragmentSize">摘要段的字符数</param>
        /// <param name="isTerm">是否条件</param>
        /// <param name="termKeyword"></param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns></returns>
        public LnQuery<T> MatchField(Expression<Func<T, string>> keySelector, string keywordClass, int fragmentSize,
            bool isTerm = false, string termKeyword = null)
        {
            var prefix = string.Format(@"<font class=""{0}"">", keywordClass);
            var suffix = "</font>";
            return MatchField(keySelector, prefix, suffix, fragmentSize, isTerm, termKeyword);
        }

        /// <summary>
        ///     设置搜索匹配的文本字段
        ///     并设置关键字的高亮颜色
        /// </summary>
        /// <param name="keySelector">字段</param>
        /// <param name="keywordColor">关键字高亮颜色</param>
        /// <param name="isTerm">是否条件</param>
        /// <param name="termKeyword"></param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns></returns>
        public LnQuery<T> MatchField(Expression<Func<T, string>> keySelector, Color keywordColor, bool isTerm,
            string termKeyword = null)
        {
            return MatchField(keySelector, keywordColor, 0, isTerm, termKeyword);
        }

        /// <summary>
        ///     设置搜索匹配的文本字段
        ///     并设置关键字的高亮颜色
        ///     返回其最佳摘要段
        /// </summary>
        /// <param name="keySelector">字段</param>
        /// <param name="keywordColor">关键字高亮颜色</param>
        /// <param name="fragmentSize">摘要段的字符数</param>
        /// <param name="isTerm">是否条件</param>
        /// <param name="termKeyword"></param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns></returns>
        public LnQuery<T> MatchField(Expression<Func<T, string>> keySelector, Color keywordColor, int fragmentSize,
            bool isTerm = false, string termKeyword = null)
        {
            var prefix = string.Format(@"<font color=""{0}"">", ColorTranslator.ToHtml(keywordColor));
            var suffix = "</font>";
            return MatchField(keySelector, prefix, suffix, fragmentSize, isTerm, termKeyword);
        }

        /// <summary>
        ///     设置搜索匹配的文本字段
        ///     并设置关键字的前缀和后缀
        ///     返回其最佳摘要段
        /// </summary>
        /// <param name="keySelector">字段</param>
        /// <param name="keywordPrefix">关键字的前缀</param>
        /// <param name="keywordSuffix">关键字的后缀</param>
        /// <param name="fragmentSize">摘要段的字符数(0为全部)</param>
        /// <param name="isTerm">是否条件</param>
        /// <param name="termKeyword"></param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns></returns>
        public LnQuery<T> MatchField(Expression<Func<T, string>> keySelector, string keywordPrefix, string keywordSuffix,
            int fragmentSize, bool isTerm, string termKeyword = null)
        {
            var property = GetLnProperty(keySelector);
            //var name = this.GetPropertyName(keySelector);
            //var property = this.properties.FirstOrDefault(item => item.Name == name);
            var matchField = new LnMatchField
            {
                Name = property.Name,
                Index = property.Index,
                IsTerm = isTerm,
                TermKeyword = termKeyword,
                FragmentSize = fragmentSize,
                KeywordPrefix = keywordPrefix,
                KeywordSuffix = keywordSuffix
            };
            matchFields.Add(matchField);
            return this;
        }

        /// <summary>
        ///     设置升序排序字段，Index.NOT_ANALYZED 才能排序
        /// </summary>
        /// <typeparam name="TKey">字段类型</typeparam>
        /// <param name="keySelector">排序字段</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns></returns>
        public LnQuery<T> OrderBy<TKey>(Expression<Func<T, TKey>> keySelector)
        {
            var property = GetLnProperty(keySelector);
            if (property.Index == Index.NOT_ANALYZED || property.Index==Index.NOT_ANALYZED_NO_NORMS)
            {
                var sortFile = new LnSortField
                {
                    Asc = true,
                    Name = property.Name,
                    SortType = (int)property.SortType
                };
                sortFields.Add(sortFile);
            }
            return this;
        }

        /// <summary>
        ///     设置降序排序字段, Index.NOT_ANALYZED 才能排序
        /// </summary>
        /// <typeparam name="TKey">字段类型</typeparam>
        /// <param name="keySelector">排序字段</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <returns></returns>
        public LnQuery<T> OrderByDescending<TKey>(Expression<Func<T, TKey>> keySelector)
        {
            var property = GetLnProperty(keySelector);
            if (property.Index == Index.NOT_ANALYZED || property.Index == Index.NOT_ANALYZED_NO_NORMS)
            {
                var sortFile = new LnSortField
                {
                    Asc = false,
                    Name = property.Name,
                    SortType = (int)property.SortType
                };
                sortFields.Add(sortFile);
            }
            return this;
        }

        /// <summary>
        ///     LN模型转换为业务Model
        /// </summary>
        /// <param name="lnModel"></param>
        /// <returns></returns>
        private T LnModelToTModel(LnModel lnModel)
        {
            var instance = Activator.CreateInstance<T>();
            foreach (var property in properties)
            {
                if (property.Store != Store.YES) continue;
                var lnField = lnModel[property.Name];
                if (lnField != null)
                {
                    property.SetValue(instance, lnField.Value);
                }
            }
            return instance;
        }

        /// <summary>
        ///     LN模型转换为业务Model
        /// </summary>
        /// <param name="lnModels">LN模型</param>
        /// <returns></returns>
        private IEnumerable<T> LnModelToTModel(IEnumerable<LnModel> lnModels)
        {
            if (lnModels == null)
            {
                yield break;
            }

            foreach (var lnModel in lnModels)
            {
                yield return LnModelToTModel(lnModel);
            }
        }

        /// <summary>
        ///     查询结果
        /// </summary>
        /// <returns></returns>
        private LnPage Query()
        {
            var query = new
            {
                IndexName = indexName,
                Keywords = keywords,
                MatchFields = matchFields.ToArray(),
                SortFields = sortFields.ToArray(),
                Skip = _skipCount,
                Take = _takeCount,
                CanSetHighLight = _canSetHighLight
                //  MaxSearchCount = maxSearchCount
            };
            return GetWebUtil.PostGetJson<LnPage>(url, null, query);
        }
        public LnIdsPage ToPageIds()
        {
            var query = new
            {
                IndexName = indexName,
                Keywords = keywords,
                MatchFields = matchFields.ToArray(),
                SortFields = sortFields.ToArray(),
                Skip = _skipCount,
                Take = _takeCount,
                CanSetHighLight = _canSetHighLight
            };
            return GetWebUtil.PostGetJson<LnIdsPage>(url, new Dictionary<string, string>
            {
                {
                    "returnIds", "Ids"
                }
            }, query);
        }
        /// <summary>
        ///     转换为ArrayOf(T)的Task对象
        /// </summary>
        /// <exception cref="SocketException"></exception>
        /// <returns></returns>
        public T[] ToArray()
        {
            return LnModelToTModel(Query().Models).ToArray();
        }

        /// <summary>
        ///     转换为ListOf(T)的Task对象
        /// </summary>
        /// <exception cref="SocketException"></exception>
        /// <returns></returns>
        public List<T> ToList()
        {
            return LnModelToTModel(Query().Models).ToList();
        }

        /// <summary>
        ///     转换为LnPageOf(T)的Task对象
        /// </summary>
        /// <exception cref="SocketException"></exception>
        /// <returns></returns>
        public LnPage<T> ToPage()
        {
            var r = Query();
            return new LnPage<T>(LnModelToTModel(r.Models), r.TotalCount);
        }

        #region Hidden

        /// <summary>
        ///     获取哈希码
        /// </summary>
        /// <returns></returns>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        ///     比较是否相等
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        /// <summary>
        ///     转换为字符串
        /// </summary>
        /// <returns></returns>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public override string ToString()
        {
            return base.ToString();
        }

        /// <summary>
        ///     获取类型
        /// </summary>
        /// <returns></returns>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public new Type GetType()
        {
            return base.GetType();
        }

        #endregion
    }
}